﻿using JuCheap.Core.Data;
using JuCheap.Core.Data.Entity;
using JuCheap.Core.Interfaces;
using JuCheap.Core.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;

namespace JuCheap.Core.Services.AppServices
{
    public class CheckWorkAttendanceService : ICheckWorkAttendanceService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly JuCheapContext _juCheapContext;
        private readonly ILogger<CheckWorkAttendanceService> _logger;

        public CheckWorkAttendanceService(IHttpClientFactory httpClientFactory, JuCheapContext juCheapContext, ILogger<CheckWorkAttendanceService> logger)
        {
            _httpClientFactory = httpClientFactory;
            _juCheapContext = juCheapContext;
            _juCheapContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
            _logger = logger;
        }

        public async Task CheckAttendance()
        {
            var users = await _juCheapContext.Workers.ToListAsync();
            var date = DateTime.Today.AddDays(-2);

            foreach (var user in users)
            {
                try
                {
                    if (await _juCheapContext.CheckWorkAttendances.AnyAsync(a => a.WorkerId == user.Id && a.Date == date))
                    {
                        return;
                    }

                    var attend = await GetUserAttendance(user, date, user.RemoteId);
                    if (attend != null)
                    {
                        _juCheapContext.CheckWorkAttendances.Add(
                            new CheckWorkAttendance
                            {
                                AddedHour = attend.Others,
                                WorkerId = user.Id,
                                FirstTime = attend.T1,
                                LastTime = attend.T2,
                                Date = date,
                                TotalHour = attend.WorkHours
                            });
                        await _juCheapContext.SaveChangesAsync();
                    }
                    else
                    {
                        _logger.LogWarning("user:{userId},{userName}, check failed !", user.Id, user.Name);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "user:{userId},{userName}, check failed !", user.Id, user.Name);
                }
            }

        }

        public async Task<Attendance> GetUserAttendance(Worker worker, DateTime date, int remoteId)
        {
            var userId = worker.Id;
            var paiban = await _juCheapContext.PaiBans.Include(p => p.BanCi)
                .FirstOrDefaultAsync(p => p.Worker.Id == userId && p.DateStart <= date && p.DateEnd >= date);
            if (paiban == null)
            {
                paiban = await _juCheapContext.PaiBans.Include(p => p.BanCi)
                    .FirstOrDefaultAsync(p => p.CompanyId == worker.CompanyId && p.DateStart <= date && p.DateEnd >= date);
            }
            if (paiban == null)
            {
                _logger.LogWarning("user {userId} not have paiban info", userId);
                return null;
            }

            var banci = paiban.BanCi.TimePeriods.Split(',').Select(int.Parse);
            var times = await _juCheapContext.TimePeriods.Where(t => banci.Contains(t.Id)).ToListAsync();
            times.Sort((t1, t2) => t1.Id - t2.Id);
            var start = date.AddHours(times.First().TimeStart - 3);
            var attend = new Attendance();
            if (!await GetRecords(remoteId, start, attend))
            {
                return null;
            }
            var h2 = 0.0;
            var total = attend.T2 - attend.T1;
            int i = 1;
            for (; i < times.Count; i++)
            {
                if (times[i - 1].TimeEnd < times[i - 1].TimeStart)
                {
                    times[i - 1].TimeEnd += 24;
                }
                if (attend.T2 >= date.AddHours(times[i - 1].TimeEnd))
                {
                    h2 += times[i - 1].TimeEnd - times[i - 1].TimeStart;
                }
                else
                {
                    h2 += (attend.T2 - date.AddHours(times[i - 1].TimeStart)).TotalHours;
                    break;
                }
                if (times[i].TimeStart < times[i - 1].TimeEnd)
                {
                    date = date.AddDays(1);
                }
            }

            if (times[i - 1].TimeEnd < times[i - 1].TimeStart)
            {
                times[i - 1].TimeEnd += 24;
            }
            if (attend.T2 >= date.AddHours(times[i - 1].TimeEnd))
            {
                h2 += times[i - 1].TimeEnd - times[i - 1].TimeStart;
                attend.Others = (attend.T2 - date.AddHours(times[i - 1].TimeEnd)).TotalHours;
            }
            else
            {
                h2 += (attend.T2 - date.AddHours(times[i - 1].TimeStart)).TotalHours;
            }

            attend.WorkHours = h2;
            return attend;
        }


       


        public async Task<bool> GetRecords(int id, DateTime start, Attendance attend)
        {
            var client = _httpClientFactory.CreateClient("api");
            var query = new List<KeyValuePair<string, string>>();
            //query.Add(KeyValuePair.Create("deviceSN", device));
            query.Add(KeyValuePair.Create("userId", id.ToString()));
            query.Add(KeyValuePair.Create("size", "100"));
            var utc = new DateTime(1970, 1, 1);
            var st = (start - utc).TotalSeconds;
            var et = (start.AddDays(1) - utc).TotalSeconds;
            query.Add(KeyValuePair.Create("startTime", st.ToString("F0")));
            query.Add(KeyValuePair.Create("endTime", et.ToString("F0")));
            Sign(query);
            var url = $"api/v1/record{BuildUrl(query)}";
            var response = await client.GetAsync(url);
            //response.EnsureSuccessStatusCode();
            var result = JsonConvert.DeserializeObject<ApiResponse<DeviceRecord>>(await response.Content.ReadAsStringAsync());
            if (result.Code != "200")
            {
                _logger.LogWarning("user with remoteId:{remoteId} get {start} record failed, reasion:{reasion}", id, start, result.Desc + result.Message);
                return false;
            }

            var t = result.Data.Data.OrderBy(r => r.signTime).ToList();
            
            var d = t.FirstOrDefault(r => r.direction == 1);
            if (d == null)
            {
                _logger.LogWarning("user with remoteid:{remoteId} record not complate., no in record.", id);
                return false;
            }
            attend.T1 = ToDateTime(d.signTime);
            d = t.FirstOrDefault(r => r.direction == 2);
            if (d == null)
            {
                _logger.LogWarning("user with remoteid:{remoteId} record not complate., no out record.", id);
                return false;
            }
            attend.T2 = ToDateTime(d.signTime);
            return true;
        }

        private static void Sign(List<KeyValuePair<string, string>> param)
        {
            var timestamp = $"{GetTimeStamp()}000";
            param.Add(KeyValuePair.Create("timestamp", timestamp));
            string appkey = ConfigurationManager.AppSettings["AppKey"];
            string appsecret = ConfigurationManager.AppSettings["AppSecret"];
            param.Add(KeyValuePair.Create("app_key", appkey));//e399af3084f4b494   hantao
            using (var md5 = MD5.Create())
            {
                var datas = md5.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}#" + appkey));//hantao 0bb30af9ea12d0d2b2594795b3e618da
                param.Add(KeyValuePair.Create("sign", string.Join("", datas.Select(b => b.ToString("x2")))));
            }
        }

        private static string BuildUrl(List<KeyValuePair<string, string>> param)
        {
            var query = string.Join('&', param.Select(kv => $"{kv.Key}={kv.Value}"));
            if (string.IsNullOrEmpty(query))
            {
                return string.Empty;
            }

            return $"?{query}";
        }

        private static long GetTimeStamp()
        {
            return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
        }

        private static DateTime ToDateTime(long timestamp)
        {
            return new DateTime(1970, 1, 1).AddSeconds(timestamp);
        }

        private class DeviceRecord
        {
            public Record[] Data { get; set; }
        }

        private class Record
        {
            public DateTime sign_time { get; set; }
            public long signTime { get; set; }
            public int direction { get; set; }
        }

        public class Attendance
        {
            public DateTime T1 { get; set; }
            public DateTime T2 { get; set; }

            public double WorkHours { get; set; }

            public double Others { get; set; }
        }
    }
}
